﻿// <copyright file="Rect3D.cs" company="Axelerate Solutions S.A.">
// Copyright (c) Axelerate Solutions S.A.  All rights reserved.
// </copyright>

namespace Axelerate.Silverlight3D.Media.Media3D
{
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.ComponentModel;

    /// <summary>
    /// File name: Rect3D.cs
    /// Company: Axelerate Solutions S.A.
    /// Created By: Daniel Rojas
    /// Created Date: 10/07/2008 15:48:30 h.
    /// Description: Represents a 3-D rectangle: for example, a cube.
    ///   <Modifications>
    ///   </Modifications>
    /// </summary>
    [TypeConverterAttribute(typeof(Rect3DConverter))]
    public struct Rect3D : IFormattable
    {
        #region Fields
        /// <summary>
        /// Value of the X coordinate of the rectangle
        /// </summary>
        private double mX;

        /// <summary>
        /// Value of the Y coordinate of the rectangle
        /// </summary>
        private double mY;

        /// <summary>
        /// Value of the Z coordinate of the rectangle
        /// </summary>
        private double mZ;
        
        /// <summary>
        /// Gets or sets the size of the Rect3D in the X dimension.
        /// </summary>
        private double mSizeX;

        /// <summary>
        /// Gets or sets the size of the Rect3D in the Y dimension.
        /// </summary>
        private double mSizeY;

        /// <summary>
        /// Gets or sets the size of the Rect3D in the Z dimension.
        /// </summary>
        private double mSizeZ;
        
        /// <summary>
        /// Gets a value indicating whether the Rec3D is empty.
        /// </summary>
        [DefaultValue(true)]
        private bool mIsEmpty;

        /// <summary>
        /// static empty Rect3D;
        /// </summary>
        private static Rect3D empty;
        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the value of the X coordinate of the rectangle.
        /// </summary>
        /// <value>Value of the X coordinate of the rectangle.</value>
        public double X
        {
            get
            {
                return mX;
            }
            set
            {
                mX = value;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets the value of the Y coordinate of the rectangle.
        /// </summary>
        /// <value>Value of the Y coordinate of the rectangle.</value>
        public double Y
        {
            get
            {
                return mY;
            }
            set
            {
                mY = value;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets the value of the Z coordinate of the rectangle.
        /// </summary>
        /// <value>Value of the Z coordinate of the rectangle.</value>
        public double Z
        {
            get
            {
                return mZ;
            }
            set
            {
                mZ = value;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets the size of the Rect3D in the X dimension.
        /// </summary>
        /// <value>Double that specifies the size of the rectangle in the X dimension.</value>
        /// <remarks>Rect3D is typically used to represent the bounds of a MeshGeometry3D or Model3D.</remarks>
        public double SizeX
        {
            get
            {
                return mSizeX;
            }
            set
            {
                mSizeX = value;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets the size of the Rect3D in the Y dimension.
        /// </summary>
        /// <value>Double that specifies the size of the rectangle in the Y dimension.</value>
        /// <remarks>Rect3D is typically used to represent the bounds of a MeshGeometry3D or Model3D.</remarks>
        public double SizeY
        {
            get
            {
                return mSizeY;
            }
            set
            {
                mSizeY = value;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// Gets or sets the size of the Rect3D in the Z dimension.
        /// </summary>
        /// <value>Double that specifies the size of the rectangle in the Z dimension.</value>
        /// <remarks>Rect3D is typically used to represent the bounds of a MeshGeometry3D or Model3D.</remarks>
        public double SizeZ
        {
            get
            {
                return mSizeZ;
            }
            set
            {
                mSizeZ = value;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public Size3D Size
        {
            get
            {
                return new Size3D();
            }
            set
            {
                this.mSizeX = value.X;
                this.mSizeY = value.Y;
                this.mSizeZ = value.Z;
                this.mIsEmpty = false;
            }
       
        }

        /// <summary>
        /// Gets or sets a Point3D that represents the origin of the Rect3D.
        /// </summary>
        /// <value>Point3D that represents the origin of the Rect3D, typically the back bottom left corner. The default value is (0,0,0).</value>
        public Point3D Location
        {
            get
            {
                return new Point3D(this.mX, this.mY, this.mZ);
            }
            set
            {
                this.mX = value.X;
                this.mY = value.Y;
                this.mZ = value.Z;
                this.mIsEmpty = false;
            }
        }

        /// <summary>
        /// Gets a value indicating whether the Rec3D is empty.
        /// </summary>
        public bool IsEmpty
        {
            get
            {
                return this.mIsEmpty;
            }
        }

        public static Rect3D Empty
        {
            get
            {
                return empty;
            }
        }
        #endregion

        #region Constructors

        static Rect3D()
        {
            Rect3D.empty = new Rect3D();
            empty.mX = double.PositiveInfinity;
            empty.mY = double.PositiveInfinity;
            empty.mZ = double.PositiveInfinity;
            empty.mSizeX = double.NegativeInfinity;
            empty.mSizeY = double.NegativeInfinity;
            empty.mSizeZ = double.NegativeInfinity;
            empty.mIsEmpty = true;
        }

        /// <summary>
        /// Creates a new Rect3D.
        /// </summary>
        /// <param name="location">Location of the new Rect3D.</param>
        /// <param name="size">Size of the new Rect3D.</param>
        public Rect3D(Point3D location, Size3D size)
        {
            this.mX = location.X;
            this.mY = location.Y;
            this.mZ = location.Z;
            if (size.X < 0 || size.Y < 0 || size.Z < 0)
            {
                throw new ArgumentException("It is not valid to set a size dimension to less than 0 in a Rect3D structure");
            }
            this.mSizeX = size.X;
            this.mSizeY = size.Y;
            this.mSizeZ = size.Z;
            this.mIsEmpty = false;

        }

        /// <summary>
        /// Creates a new Rect3D.
        /// </summary>
        /// <param name="x">X-axis coordinate of the new Rect3D.</param>
        /// <param name="y">Y-axis coordinate of the new Rect3D.</param>
        /// <param name="z">Z-axis coordinate of the new Rect3D.</param>
        /// <param name="sizeX">Size of the new Rect3D in the X dimension.</param>
        /// <param name="sizeY">Size of the new Rect3D in the Y dimension.</param>
        /// <param name="sizeZ">Size of the new Rect3D in the Z dimension.</param>
        public Rect3D(double x, double y, double z, double sizeX, double sizeY, double sizeZ)
        {
            this.mX = x;
            this.mY = y;
            this.mZ = z;
            if (sizeX < 0 || sizeY < 0 || sizeZ < 0)
            {
                throw new ArgumentException("It is not valid to set a size dimension to less than 0 in a Rect3D structure");
            }
            this.mSizeX = sizeX;
            this.mSizeY = sizeY;
            this.mSizeZ = sizeZ;
            this.mIsEmpty = false;
        }
        #endregion

        #region Operators
        
        /// <summary>
        /// Compares two Rect3D instances for exact equality.
        /// </summary>
        /// <param name="rect1">First Rect3D to evaluate.</param>
        /// <param name="rect2">Second Rect3D to evaluate.</param>
        /// <returns>True if the two Rect3D instances are exactly equal; false otherwise.</returns>
        public static bool operator ==(Rect3D rect1, Rect3D rect2)
        {
            return Rect3D.Equals(rect1, rect2);
        }

        /// <summary>
        /// Compares two Rect3D instances for exact inequality.
        /// </summary>
        /// <param name="rect1">First Rect3D to compare.</param>
        /// <param name="rect2">First Rect3D to compare.</param>
        /// <returns>First Rect3D to compare.</returns>
        public static bool operator !=(Rect3D rect1, Rect3D rect2)
        {
            return !Rect3D.Equals(rect1, rect2);
        }

        #endregion

        #region Methods
        /// <summary>
        /// Creates a string representation of the Rect3D.
        /// </summary>
        /// <returns>String representation of the Rect3D.</returns>
        /// <remarks>Rect3D is typically used to represent the bounds of a MeshGeometry3D or Model3D.</remarks>
        public override string ToString()
        {
            if (!this.IsEmpty)
            {
                return string.Format("{0},{1},{2},{3},{4},{5}", this.mX, this.mY, this.mZ, this.mSizeX, this.mSizeY, this.mSizeZ);
            }
            else
            {
                return "Empty";
            }
        }

        /// <summary>
        /// Creates a string representation of the Rect3D.
        /// </summary>
        /// <param name="provider">Culture-specific formatting information.</param>
        /// <returns>String representation of the Rect3D.</returns>
        public string ToString(IFormatProvider provider)
        {
            if (!this.IsEmpty)
            {
                return string.Format(provider, "{0},{1},{2},{3},{4},{5}", this.mX, this.mY, this.mZ, this.mSizeX, this.mSizeY, this.mSizeZ);
            }
            else
            {
                return "Empty";
            }
        }

        /// <summary>
        /// Converts a string representation of a Rect3D into the equivalent Rect3D structure.
        /// </summary>
        /// <param name="source">String that represents a Rect3D.</param>
        /// <returns>Rect3D represented by the string.</returns>
        public static Rect3D Parse(string source)
        {
            if (source == "Empty")
            {
                return Rect3D.Empty;
            }
            else
            {
                string[] parts = source.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length == 5)
                {
                    return new Rect3D(double.Parse(parts[0]), double.Parse(parts[1]), double.Parse(parts[2]), double.Parse(parts[3]), double.Parse(parts[4]), double.Parse(parts[5]));
                }
                else
                {
                    throw new FormatException("source come in incorret format");
                }
            }
        }

        /// <summary>
        /// Gets a value that indicates whether a specified Point3D is within the Rect3D, including its edges.
        /// </summary>
        /// <param name="point">Point to be tested.</param>
        /// <returns>True if the specified point or rectangle is within the Rect3D, including its edges; false otherwise.</returns>
        public bool Contains(Point3D point)
        {
            return Contains(point.X, point.Y, point.Z);
        }

        /// <summary>
        /// Gets a value that indicates whether a specified Point3D is within the Rect3D, including its edges.
        /// </summary>
        /// <param name="rect">Rect3D to be tested.</param>
        /// <returns>True if the specified point or rectangle is within the Rect3D, including its edges; false otherwise.</returns>
        public bool Contains(Rect3D rect)
        {
            if (IsEmpty)
            {
                return false;
            }

            return (rect.X >= this.X) &&
                   (rect.Y >= this.Y) &&
                   (rect.Z >= this.Z) &&
                   (rect.X + rect.SizeX <= this.X + this.SizeX) &&
                   (rect.Y + rect.SizeY <= this.Y + this.SizeY) &&
                   (rect.Z + rect.SizeZ <= this.Z + this.SizeZ);
        }

        /// <summary>
        /// Gets a value that indicates whether a specified Point3D is within the Rect3D, including its edges.
        /// </summary>
        /// <param name="x">X-axis coordinate of the Point3D to be tested.</param>
        /// <param name="y">Y-axis coordinate of the Point3D to be tested.</param>
        /// <param name="z">Z-coordinate of the Point3D to be tested.</param>
        /// <returns>True if the specified point or rectangle is within the Rect3D, including its edges; false otherwise.</returns>
        public bool Contains(double x, double y, double z)
        {
            if (IsEmpty)
            {
                return false;
            }

            return (x >= this.X) &&
                   (x <= (this.X + this.SizeX)) &&
                   (y >= this.Y) &&
                   (y <= (this.Y + this.SizeY)) &&
                   (z >= this.Z) &&
                   (z <= (this.Z + this.SizeZ));
        }

        /// <summary>
        /// Updates a specified Rect3D to reflect the union of that Rect3D and a specified Point3D.
        /// </summary>
        /// <param name="point">The Point3D whose union with the specified Rect3D is to be evaluated.</param>
        public void Union(Point3D point)
        {
            this.Union(new Rect3D(point.X, point.Y, point.Z, 0, 0, 0));
        }

        /// <summary>
        /// Updates a specified Rect3D to reflect the union of that Rect3D and a second specified Rect3D.
        /// </summary>
        /// <param name="rect">The Rect3D whose union with the current Rect3D is to be evaluated.</param>
        public void Union(Rect3D rect)
        {
            if (IsEmpty)
            {
                this = rect;
            }
            else
            {
                double minX = System.Math.Min(this.X, rect.X);
                double minY = System.Math.Min(this.Y, rect.Y);
                double minZ = System.Math.Min(this.Z, rect.Z);

                this.SizeX = System.Math.Max(this.X + this.SizeX, rect.X + rect.SizeX) - minX;
                this.SizeY = System.Math.Max(this.Y + this.SizeY, rect.Y + rect.SizeY) - minY;
                this.SizeZ = System.Math.Max(this.Z + this.SizeZ, rect.Z + rect.SizeZ) - minZ;
                this.X = minX;
                this.Y = minY;
                this.Z = minZ;
            }
        }

        /// <summary>
        /// Returns a new Rect3D that represents the union of a Rect3D, and a specified Point3D.
        /// </summary>
        /// <param name="rect">The Rect3D whose union with the current Rect3D is to be evaluated.</param>
        /// <param name="point">The Point3D whose union with the specified Rect3D is to be evaluated.</param>
        /// <returns>Result of the union of rect and point.</returns>
        public static Rect3D Union(Rect3D rect, Point3D point)
        {
            Rect3D newRec = new Rect3D(rect.Location, rect.Size);
            newRec.Union(point);
            return newRec;
        }

        /// <summary>
        /// Returns a new instance of Rect3D that represents the union of two Rect3D objects.
        /// </summary>
        /// <param name="rect1">First Rect3D.</param>
        /// <param name="rect2">First Rect3D.</param>
        /// <returns></returns>
        public static Rect3D Union(Rect3D rect1, Rect3D rect2)
        {
            Rect3D newRec = new Rect3D(rect1.Location, rect1.Size);
            newRec.Union(rect2);
            return newRec;
        }

        /// <summary>
        /// Compares two Rect3D instances for equality.
        /// </summary>
        /// <param name="rect1">First Rect3D to compare.</param>
        /// <param name="rect2">Second Rect3D to compare.</param>
        /// <returns>True if the two specified Rect3D instances are exactly equal; false otherwise.</returns>
        /// <remarks>Note that values of type Double might acquire error when operated upon; an exact comparison between two logically-equal values might fail.</remarks>
        public static bool Equals(Rect3D rect1, Rect3D rect2)
        {
            return rect1.mX == rect2.mX && rect1.mY == rect2.mY && rect1.mZ == rect2.mZ && rect1.mSizeX == rect2.mSizeX && rect1.mSizeY == rect2.mSizeY && rect1.mSizeZ == rect2.mSizeZ;
        }

        /// <summary>
        /// Compares two Rect3D instances for equality.
        /// </summary>
        /// <param name="value">The Rect3D to compare to "this"</param>
        /// <returns>True if the two specified Rect3D instances are exactly equal; false otherwise.</returns>
        /// <remarks>Note that values of type Double might acquire error when operated upon; an exact comparison between two logically-equal values might fail.</remarks>
        public bool Equals(Rect3D value)
        {
            return Rect3D.Equals(this, value);
        }

        /// <summary>
        /// Compares two Rect3D instances for equality.
        /// </summary>
        /// <param name="o">Type: System.Object</param>
        /// <returns>True if the two specified Rect3D instances are exactly equal; false otherwise.</returns>
        /// <remarks>Note that values of type Double might acquire error when operated upon; an exact comparison between two logically-equal values might fail.</remarks>
        public override bool Equals(object o)
        {
            if (o is Rect3D)
            {
                return this.Equals((Rect3D)o);
            }
            else
            {
                return false;
            }
        }

        public override int GetHashCode()
        {
            return this.mX.GetHashCode() | this.mY.GetHashCode() | this.mZ.GetHashCode() |
                   this.mSizeX.GetHashCode() | this.mSizeY.GetHashCode() | this.mSizeZ.GetHashCode();
        }
        #endregion

        #region IFormattable Members

        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (!this.IsEmpty)
            {
                return string.Format(formatProvider, format, this.mX, this.mY, this.mZ, this.mSizeX, this.mSizeY, this.mSizeZ);
            }
            else
            {
                return "";
            }
        }

        #endregion
    }
}
